package top.luowenbo.mydb.backend.parser;

import top.luowenbo.mydb.backend.parser.Tokenizer;
import top.luowenbo.mydb.backend.parser.statement.*;
import top.luowenbo.mydb.common.Error;

import java.util.ArrayList;
import java.util.List;

public class dfaParser {
    public static Object dfaParse(byte[] statement) throws Exception{
        Tokenizer tokenizer = new Tokenizer(statement);
        String token = tokenizer.peek();
        tokenizer.pop();
        Struct struct = new Struct();
        struct.step = "stepType";
        while(true){
            try{
                switch (struct.step){
                    case "stepType":
                        String stepType = token.toUpperCase();
                        switch (stepType) {
                            case "BEGIN":
                                return parseBegin(tokenizer);
                            case "COMMIT":
                                if(!"".equals(tokenizer.peek())) {
                                    throw Error.InvalidCommandException;
                                }
                                return new Commit();
                            case "ABORT":
                                if(!"".equals(tokenizer.peek())) {
                                    throw Error.InvalidCommandException;
                                }
                                return new Abort();
                            case "CREATE":
                                struct.type = "create";
                                struct.step = "stepDDLTable";
                                break;
                            case "DROP":
                                struct.type = "drop";
                                struct.step = "stepDDLTable";
                                break;
                            case "SELECT":
                                struct.type = "select";
                                struct.step = "stepFiledNames";
                                break;
                            case "INSERT":
                                struct.type = "insert";
                                struct.step = "stepInto";
                                break;
                            case "DELETE":
                                struct.type = "delete";
                                struct.step = "stepFrom";
                                break;
                            case "UPDATE":
                                struct.type = "update";
                                struct.step = "stepTableName";
                                break;
                            case "SHOW":
                                String tmp = tokenizer.peek();
                                if("".equals(tmp)) {
                                    return new Show();
                                }
                                throw Error.InvalidCommandException;
                            default:
                                throw Error.InvalidCommandException;
                        }
                        break;
                    case "stepDDLTable":
                        if(!"table".equals(tokenizer.peek())){
                            throw Error.InvalidCommandException;
                        }
                        tokenizer.pop();
                        struct.step = "stepTableName";
                        break;
                    case "stepInto":
                        if(!"into".equals(tokenizer.peek())) {
                            throw Error.InvalidCommandException;
                        }
                        tokenizer.pop();
                        struct.step = "stepTableName";
                        break;
                    case "stepFiledNames":
                        if(struct.type.equals("select")){
                            struct = stepSelectFieldNames(tokenizer, struct);
                            struct.step = "stepFrom";
                        }else if(struct.type.equals("update")){
                            if(tokenizer.peek() != ""){
                                String[] filed = new String[1];
                                filed[0] = tokenizer.peek();
                                tokenizer.pop();
                                struct.fieldNames = filed;
                                struct.step = "stepEqual";
                            }
                        }
                        break;
                    case "stepEqual":
                        if( ! tokenizer.peek().equals("=")){
                            throw Error.InvalidCommandException;
                        }
                        tokenizer.pop();
                        struct.step = "stepValue";
                        break;
                    case "stepFrom":
                        if( ! "from".equals(tokenizer.peek())){
                            throw Error.InvalidCommandException;
                        }
                        tokenizer.pop();
                        struct.step = "stepTableName";
                        break;
                    case "stepTableName":
                        String tableName = tokenizer.peek();
                        if(!isName(tableName)) {
                            throw Error.InvalidCommandException;
                        }
                        struct.tableName = tableName;
                        tokenizer.pop();
                        if(struct.type.equals("select") || struct.type.equals("delete")){
                            struct.step = "stepWhere";
                        }else if(struct.type.equals("insert")){
                            struct.step = "stepValues";
                        }else if(struct.type.equals("update")){
                            struct.step = "stepSet";
                        }else if(struct.type.equals("drop") && "".equals(tokenizer.peek())){
                            Drop drop = new Drop();
                            drop.tableName = tableName;
                            return drop;
                        }else if(struct.type.equals("create")){
                            struct.step = "stepCreate";
                            struct.type = "create";
                        }else{
                            throw Error.InvalidCommandException;
                        }
                        break;
                    case "stepCreate":
                        return parseCreate(tokenizer);
                    case "stepSet":
                        if(!"set".equals(tokenizer.peek())) {
                            throw Error.InvalidCommandException;
                        }
                        tokenizer.pop();
                        struct.step = "stepFiledNames";
                        break;
                    case "stepValues":
                        if(!"values".equals(tokenizer.peek())) {
                            throw Error.InvalidCommandException;
                        }
                        tokenizer.pop();
                        struct.step = "stepValue";
                    case "stepValue":
                        List<String> values = new ArrayList<>();
                        while(true) {
                            String value = tokenizer.peek();

                            if("".equals(value)) {
                                break;
                            } else {
                                values.add(value);
                            }
                            tokenizer.pop();
                        }
                        struct.values = values.toArray(new String[values.size()]);
                        if(struct.type.equals("insert")) {
                            struct.step = "finalState";
                        }else if(struct.type.equals("update")){
                            struct.step = "stepWhere";
                        }
                        break;
                    case "stepWhere":
                        String tmp = tokenizer.peek();
                        if("".equals(tmp)) {
                            struct.where = null;
                            struct.step = "finalState";
                        }else if ("where".equals(tmp)){
                            struct.step = "stepParserWhere";
                        }else {
                            throw Error.InvalidCommandException;
                        }
                        tokenizer.pop();
                        break;
                    case "finalState":
                        if(struct.type.equals("select")){
                            Select select = new Select();
                            select.fields = struct.fieldNames;
                            select.tableName = struct.tableName;
                            if(struct.where != null){
                                select.where = struct.where;
                            }
                            return select;
                        }else if(struct.type.equals("insert")){
                            Insert insert = new Insert();
                            insert.tableName = struct.tableName;
                            insert.values = struct.values;
                            return insert;
                        }else if(struct.type.equals("update")){
                            Update update = new Update();
                            update.tableName = struct.tableName;
                            update.fieldName = struct.fieldNames[0];
                            update.where = struct.where;
                            update.value = struct.values[0];
                            return update;
                        }else if(struct.type.equals("delete")){
                            Delete delete = new Delete();
                            delete.tableName = struct.tableName;
                            delete.where = struct.where;
                            return delete;
                        }
                    case "stepParserWhere":
                        Where where = new Where();
                        SingleExpression exp1 = parseSingleExp(tokenizer);
                        where.singleExp1 = exp1;

                        String logicOp = tokenizer.peek();
                        if("".equals(logicOp)) {
                            where.logicOp = logicOp;
                            struct.where = where;

                            struct.step = "finalState";
                            break;
                        }
                        if(!isLogicOp(logicOp)) {
                            throw Error.InvalidCommandException;
                        }
                        where.logicOp = logicOp;
                        tokenizer.pop();

                        SingleExpression exp2 = parseSingleExp(tokenizer);
                        where.singleExp2 = exp2;

                        if(!"".equals(tokenizer.peek())) {
                            throw Error.InvalidCommandException;
                        }
                        struct.where = where;
                        struct.step = "finalState";
                        break;
                    case "":
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }
    private static Create parseCreate(Tokenizer tokenizer) throws Exception {
        Create create = new Create();
        List<String> fNames = new ArrayList<>();
        List<String> fTypes = new ArrayList<>();
        while(true) {
            tokenizer.pop();
            String field = tokenizer.peek();
            if ("(".equals(field)) {
                break;
            }

            if (!isName(field)) {
                throw Error.InvalidCommandException;
            }

            tokenizer.pop();
            String fieldType = tokenizer.peek();
            if (!isType(fieldType)) {
                throw Error.InvalidCommandException;
            }
            fNames.add(field);
            fTypes.add(fieldType);
            tokenizer.pop();

            String next = tokenizer.peek();
            if (",".equals(next)) {
                continue;
            } else if ("".equals(next)) {
                throw Error.TableNoIndexException;
            } else if ("(".equals(next)) {
                break;
            } else {
                throw Error.InvalidCommandException;
            }
        }
        create.fieldName = fNames.toArray(new String[fNames.size()]);
        create.fieldType = fTypes.toArray(new String[fTypes.size()]);

        tokenizer.pop();
        if(!"index".equals(tokenizer.peek())) {
            throw Error.InvalidCommandException;
        }

        List<String> indexes = new ArrayList<>();
        while(true) {
            tokenizer.pop();
            String field = tokenizer.peek();
            if(")".equals(field)) {
                break;
            }
            if(!isName(field)) {
                throw Error.InvalidCommandException;
            } else {
                indexes.add(field);
            }
        }
        create.index = indexes.toArray(new String[indexes.size()]);
        tokenizer.pop();

        if(!"".equals(tokenizer.peek())) {
            throw Error.InvalidCommandException;
        }
        return create;
    }
    private static boolean isType(String tp) {
        return ("int32".equals(tp) || "int64".equals(tp) ||
                "string".equals(tp));
    }
    private static Begin parseBegin(Tokenizer tokenizer) throws Exception {
        String isolation = tokenizer.peek();
        Begin begin = new Begin();
        if("".equals(isolation)) {
            return begin;
        }
        if(!"isolation".equals(isolation)) {
            throw Error.InvalidCommandException;
        }

        tokenizer.pop();
        String level = tokenizer.peek();
        if(!"level".equals(level)) {
            throw Error.InvalidCommandException;
        }
        tokenizer.pop();

        String tmp1 = tokenizer.peek();
        if("read".equals(tmp1)) {
            tokenizer.pop();
            String tmp2 = tokenizer.peek();
            if("committed".equals(tmp2)) {
                tokenizer.pop();
                if(!"".equals(tokenizer.peek())) {
                    throw Error.InvalidCommandException;
                }
                return begin;
            } else {
                throw Error.InvalidCommandException;
            }
        } else if("repeatable".equals(tmp1)) {
            tokenizer.pop();
            String tmp2 = tokenizer.peek();
            if("read".equals(tmp2)) {
                begin.isRepeatableRead = true;
                tokenizer.pop();
                if(!"".equals(tokenizer.peek())) {
                    throw Error.InvalidCommandException;
                }
                return begin;
            } else {
                throw Error.InvalidCommandException;
            }
        } else {
            throw Error.InvalidCommandException;
        }
    }
    private static Struct stepSelectFieldNames(Tokenizer tokenizer, Struct struct) throws Exception{
        List<String> fields = new ArrayList<>();
        String asterisk = tokenizer.peek();
        if("*".equals(asterisk)) {
            fields.add(asterisk);
            tokenizer.pop();
        } else {
            while(true) {
                String field = tokenizer.peek();
                if(!isName(field)) {
                    throw Error.InvalidCommandException;
                }
                fields.add(field);
                tokenizer.pop();
                if(",".equals(tokenizer.peek())) {
                    tokenizer.pop();
                } else {
                    break;
                }
            }
        }
        struct.fieldNames = fields.toArray(new String[fields.size()]);
        return struct;
    }
    private static boolean isName(String name) {
        return !(name.length() == 1 && !Tokenizer.isAlphaBeta(name.getBytes()[0]));
    }
    private static SingleExpression parseSingleExp(Tokenizer tokenizer) throws Exception {
        SingleExpression exp = new SingleExpression();

        String field = tokenizer.peek();
        if(!isName(field)) {
            throw Error.InvalidCommandException;
        }
        exp.field = field;
        tokenizer.pop();

        String op = tokenizer.peek();
        if(!isCmpOp(op)) {
            throw Error.InvalidCommandException;
        }
        exp.compareOp = op;
        tokenizer.pop();

        exp.value = tokenizer.peek();
        tokenizer.pop();
        return exp;
    }
    private static boolean isCmpOp(String op) {
        return ("=".equals(op) || ">".equals(op) || "<".equals(op));
    }
    private static boolean isLogicOp(String op) {
        return ("and".equals(op) || "or".equals(op));
    }
}
